package com.absinthe.libchecker.features.applist.detail.ui.base

import android.content.ContentValues
import android.content.Context
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.view.Gravity
import android.view.View
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.TextView
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.viewbinding.ViewBinding
import com.absinthe.libchecker.BuildConfig
import com.absinthe.libchecker.R
import com.absinthe.libchecker.annotation.ACTION
import com.absinthe.libchecker.annotation.ACTION_IN_RULES
import com.absinthe.libchecker.annotation.ACTIVITY
import com.absinthe.libchecker.annotation.ET_NOT_ELF
import com.absinthe.libchecker.annotation.NATIVE
import com.absinthe.libchecker.annotation.PERMISSION
import com.absinthe.libchecker.annotation.isComponentType
import com.absinthe.libchecker.compat.VersionCompat
import com.absinthe.libchecker.compat.ZipFileCompat
import com.absinthe.libchecker.constant.GlobalValues
import com.absinthe.libchecker.features.applist.DetailFragmentManager
import com.absinthe.libchecker.features.applist.MODE_SORT_BY_LIB
import com.absinthe.libchecker.features.applist.Referable
import com.absinthe.libchecker.features.applist.Sortable
import com.absinthe.libchecker.features.applist.detail.DetailViewModel
import com.absinthe.libchecker.features.applist.detail.IDetailContainer
import com.absinthe.libchecker.features.applist.detail.ui.ELFDetailDialogFragment
import com.absinthe.libchecker.features.applist.detail.ui.EXTRA_PACKAGE_NAME
import com.absinthe.libchecker.features.applist.detail.ui.LibDetailDialogFragment
import com.absinthe.libchecker.features.applist.detail.ui.PermissionDetailDialogFragment
import com.absinthe.libchecker.features.applist.detail.ui.adapter.LibStringAdapter
import com.absinthe.libchecker.features.applist.detail.ui.impl.ComponentsAnalysisFragment
import com.absinthe.libchecker.features.applist.detail.ui.impl.MetaDataAnalysisFragment
import com.absinthe.libchecker.features.applist.detail.ui.impl.NativeAnalysisFragment
import com.absinthe.libchecker.features.applist.detail.ui.impl.PermissionAnalysisFragment
import com.absinthe.libchecker.features.applist.detail.ui.view.EmptyListView
import com.absinthe.libchecker.features.statistics.bean.LibStringItem
import com.absinthe.libchecker.features.statistics.bean.LibStringItemChip
import com.absinthe.libchecker.integrations.anywhere.AnywhereManager
import com.absinthe.libchecker.integrations.blocker.BlockerManager
import com.absinthe.libchecker.integrations.monkeyking.MonkeyKingManager
import com.absinthe.libchecker.integrations.monkeyking.ShareCmpInfo
import com.absinthe.libchecker.ui.base.BaseAlertDialogBuilder
import com.absinthe.libchecker.ui.base.BaseFragment
import com.absinthe.libchecker.utils.OsUtils
import com.absinthe.libchecker.utils.UiUtils
import com.absinthe.libchecker.utils.extensions.addPaddingTop
import com.absinthe.libchecker.utils.extensions.doOnMainThreadIdle
import com.absinthe.libchecker.utils.extensions.dp
import com.absinthe.libchecker.utils.extensions.getColor
import com.absinthe.libchecker.utils.extensions.launchLibReferencePage
import com.absinthe.libchecker.utils.extensions.reverseStrikeThroughAnimation
import com.absinthe.libchecker.utils.extensions.startStrikeThroughAnimation
import com.absinthe.libchecker.utils.extensions.unsafeLazy
import com.absinthe.libchecker.utils.showToast
import com.absinthe.libraries.utils.utils.AntiShakeUtils
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rikka.core.util.ClipboardUtils
import timber.log.Timber

/**
 * <pre>
 * author : Absinthe
 * time : 2020/11/27
 * </pre>
 */

const val EXTRA_TYPE = "EXTRA_TYPE"

abstract class BaseDetailFragment<T : ViewBinding> :
  BaseFragment<T>(),
  Sortable {

  protected val viewModel: DetailViewModel by activityViewModels()
  protected val packageName by lazy { arguments?.getString(EXTRA_PACKAGE_NAME).orEmpty() }
  protected val type by lazy { arguments?.getInt(EXTRA_TYPE) ?: NATIVE }
  protected val adapter by lazy { LibStringAdapter(packageName, type, childFragmentManager) }
  protected val emptyView by unsafeLazy {
    EmptyListView(requireContext()).apply {
      layoutParams = FrameLayout.LayoutParams(
        FrameLayout.LayoutParams.MATCH_PARENT,
        FrameLayout.LayoutParams.WRAP_CONTENT
      ).also {
        it.gravity = Gravity.CENTER_HORIZONTAL
      }
      addPaddingTop(96.dp)
      text.text = getString(R.string.loading)
    }
  }
  private val dividerItemDecoration by lazy {
    DividerItemDecoration(
      requireContext(),
      DividerItemDecoration.VERTICAL
    )
  }
  protected var isListReady = false
  private var afterListReadyTask: Runnable? = null
  private var integrationMonkeyKingBlockList: List<ShareCmpInfo.Component>? = null
  private var integrationBlockerList: List<ShareCmpInfo.Component>? = null

  abstract fun getRecyclerView(): RecyclerView

  protected abstract val needShowLibDetailDialog: Boolean

  protected abstract suspend fun getItems(): List<LibStringItemChip>
  protected abstract fun onItemsAvailable(items: List<LibStringItemChip>)

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    lifecycleScope.launch(Dispatchers.IO) {
      val items = getItems()
      withContext(Dispatchers.Main) {
        onItemsAvailable(items)
      }
    }
  }

  override fun onAttach(context: Context) {
    super.onAttach(context)
    if (context is IDetailContainer) {
      context.detailFragmentManager.register(type, this)
    }
    if (DetailFragmentManager.navType != DetailFragmentManager.NAV_TYPE_NONE) {
      setupListReadyTask()
    }
    adapter.apply {
      if (needShowLibDetailDialog) {
        setOnItemClickListener { _, view, position ->
          if (AntiShakeUtils.isInvalidClick(view)) {
            return@setOnItemClickListener
          }
          openLibDetailDialog(position)
        }
      }
      setOnItemLongClickListener { _, _, position ->
        doOnLongClick(context, getItem(position), position)
        true
      }
      setProcessMode(GlobalValues.processMode)
    }
  }

  override fun onDetach() {
    super.onDetach()
    activity?.let {
      if (it is IDetailContainer) {
        (it as IDetailContainer).detailFragmentManager.unregister(type)
      }
    }
  }

  override fun onVisibilityChanged(visible: Boolean) {
    super.onVisibilityChanged(visible)
    if (visible) {
      val processMap = if (isComponentFragment()) {
        viewModel.processesMap
      } else if (isNativeSourceAvailable()) {
        viewModel.nativeSourceMap
      } else if (hasNonGrantedPermissions()) {
        val label = requireContext().getString(R.string.permission_not_granted)
        val color = R.color.material_red_400.getColor(requireContext())
        mapOf(label to color)
      } else {
        emptyMap()
      }
      viewModel.updateProcessMap(processMap)
      viewModel.updateProcessToolIconVisibility(processMap.isNotEmpty() && !hasNonGrantedPermissions())
    }
  }

  override suspend fun sort() {
    val list = mutableListOf<LibStringItemChip>().also {
      it += adapter.data
    }
    val itemChip =
      if (adapter.highlightPosition != -1 && adapter.highlightPosition < adapter.data.size) {
        adapter.data[adapter.highlightPosition]
      } else {
        null
      }

    if (GlobalValues.libSortMode == MODE_SORT_BY_LIB) {
      if (type == NATIVE) {
        list.sortByDescending { it.item.size }
      } else {
        list.sortByDescending { it.item.name }
      }
    } else {
      list.sortWith(compareByDescending<LibStringItemChip> { it.rule != null }.thenBy { it.item.name })
    }

    if (itemChip != null) {
      val newHighlightPosition = list.indexOf(itemChip)
      adapter.setHighlightBackgroundItem(newHighlightPosition)
    }

    withContext(Dispatchers.Main) {
      adapter.setDiffNewData(list)
    }
  }

  private fun sortedList(origin: MutableList<LibStringItemChip>): MutableList<LibStringItemChip> {
    if (GlobalValues.libSortMode == MODE_SORT_BY_LIB) {
      if (type == NATIVE) {
        origin.sortByDescending { it.item.size }
      } else {
        origin.sortByDescending { it.item.name }
      }
    } else {
      origin.sortWith(compareByDescending<LibStringItemChip> { it.rule != null }.thenBy { it.item.name })
    }
    return origin
  }

  protected open suspend fun getFilterList(
    searchWords: String?,
    process: String?
  ): List<LibStringItemChip>? {
    return getItems().asSequence()
      .filter {
        searchWords == null || it.item.name.contains(
          searchWords,
          true
        ) || it.item.source?.contains(searchWords, true) == true
      }
      .filter { process == null || it.item.process == process }
      .toList()
  }

  override suspend fun setItemsWithFilter(searchWords: String?, process: String?) {
    adapter.highlightText = searchWords.orEmpty()
    getFilterList(searchWords, process)?.let {
      val sortedList = sortedList(it.toMutableList())
      lifecycleScope.launch(Dispatchers.Main) {
        if (isDetached || !isBindingInitialized()) return@launch
        if (sortedList.isEmpty()) {
          if (getRecyclerView().itemDecorationCount > 0) {
            getRecyclerView().removeItemDecoration(dividerItemDecoration)
          }
          emptyView.text.text = getString(R.string.empty_list)
        } else {
          if (getRecyclerView().itemDecorationCount == 0) {
            getRecyclerView().addItemDecoration(dividerItemDecoration)
          }
        }
        adapter.setDiffNewData(sortedList) {
          afterListReadyTask?.run()
          viewModel.updateItemsCountStateFlow(type, sortedList.size)
        }
      }
    }
  }

  fun switchProcessMode() {
    if (isComponentFragment() || isNativeSourceAvailable()) {
      adapter.switchProcessMode()
    }
  }

  fun setupListReadyTask() {
    if (DetailFragmentManager.navType == type) {
      DetailFragmentManager.navComponent?.let {
        afterListReadyTask = Runnable {
          navigateToComponentImpl(it)
        }
      }
      DetailFragmentManager.resetNavigationParams()
    }
  }

  private fun navigateToComponentImpl(component: String) {
    var componentPosition = adapter.data.indexOfFirst { it.item.name == component }
    if (componentPosition == -1) {
      return
    }
    if (adapter.hasHeaderLayout()) {
      componentPosition++
    }

    Timber.d("navigateToComponent: componentPosition = $componentPosition")

    doOnMainThreadIdle {
      (activity as? IDetailContainer)?.collapseAppBar()
      getRecyclerView().scrollToPosition(componentPosition.coerceAtMost(adapter.itemCount - 1))

      // Calculate better offset to provide improved visual experience for highlighting
      val recyclerView = getRecyclerView()
      // Place highlighted item about 1/4 from top for better visibility
      val centerOffset = recyclerView.height / 4

      with(recyclerView.layoutManager) {
        if (this is LinearLayoutManager) {
          scrollToPositionWithOffset(componentPosition, centerOffset)
        } else if (this is StaggeredGridLayoutManager) {
          scrollToPositionWithOffset(componentPosition, centerOffset)
        }
      }
    }

    adapter.setHighlightBackgroundItem(componentPosition)
    //noinspection NotifyDataSetChanged
    adapter.notifyDataSetChanged()
  }

  private fun openLibDetailDialog(position: Int) {
    if (position < 0 || position >= adapter.itemCount) {
      return
    }
    val item = adapter.getItem(position)
    val isValidLib = item.rule != null

    if (adapter.type == PERMISSION) {
      PermissionDetailDialogFragment.newInstance(item.item.name)
        .show(childFragmentManager, PermissionDetailDialogFragment::class.java.name)
      return
    }

    lifecycleScope.launch(Dispatchers.IO) {
      val name = item.rule?.libName ?: item.item.name
      val regexName = item.rule?.regexName
      val libType = if (item.rule?.libType == ACTION_IN_RULES) ACTION else adapter.type

      withContext(Dispatchers.Main) {
        LibDetailDialogFragment.newInstance(name, libType, regexName, isValidLib)
          .show(childFragmentManager, LibDetailDialogFragment::class.java.name)
      }
    }
  }

  fun isComponentFragment(): Boolean {
    return isComponentType(type)
  }

  fun isNativeSourceAvailable(): Boolean {
    return type == NATIVE && viewModel.nativeSourceMap.isNotEmpty()
  }

  fun hasNonGrantedPermissions(): Boolean {
    return type == PERMISSION && viewModel.permissionsItems.value?.any { it.item.size == 0L } == true
  }

  private fun doOnLongClick(context: Context, item: LibStringItemChip, position: Int) {
    val packageName = viewModel.apkPreviewInfo?.packageName ?: viewModel.packageInfo.packageName
    val actionMap = mutableMapOf<Int, () -> Unit>()
    val arrayAdapter = ArrayAdapter<String>(context, android.R.layout.simple_list_item_1)
    var componentName = item.item.name
    if (this is PermissionAnalysisFragment) {
      componentName = componentName.substringBefore(" ")
    }
    val fullComponentName = if (componentName.startsWith(".")) {
      packageName + componentName
    } else {
      componentName
    }

    // Copy
    arrayAdapter.add(getString(android.R.string.copy))
    actionMap[arrayAdapter.count - 1] = {
      if (this is MetaDataAnalysisFragment) {
        ClipboardUtils.put(context, componentName + ": " + item.item.source)
      } else {
        ClipboardUtils.put(context, componentName)
      }
      VersionCompat.showCopiedOnClipboardToast(context)
    }

    // ELF info
    if (!viewModel.isApkPreview && this is NativeAnalysisFragment) {
      arrayAdapter.add(getString(R.string.lib_detail_elf_extract))
      actionMap[arrayAdapter.count - 1] = {
        val loading = UiUtils.createLoadingDialog(requireActivity())
        lifecycleScope.launch {
          withContext(Dispatchers.Main) {
            loading.show()
          }

          val result = extractElf(item.item)

          withContext(Dispatchers.Main) {
            loading.dismiss()
            result.onSuccess {
              context.showToast(R.string.lib_detail_elf_extract_success)
            }.onFailure { e ->
              Timber.e(e, "Failed to extract ELF: ${item.item}")
              context.showToast(R.string.lib_detail_elf_extract_failed)
            }
          }
        }
      }

      if (item.item.elfInfo.elfType != ET_NOT_ELF) {
        arrayAdapter.add(getString(R.string.lib_detail_elf_info))
        actionMap[arrayAdapter.count - 1] = {
          ELFDetailDialogFragment.newInstance(
            packageName = packageName,
            elfPath = item.item.source.orEmpty(),
            ruleIcon = item.rule?.iconRes
              ?: com.absinthe.lc.rulesbundle.R.drawable.ic_sdk_placeholder
          ).show(childFragmentManager, ELFDetailDialogFragment::class.java.name)
        }
      }
    }

    // Reference
    if (this is Referable && !componentName.startsWith(".")) {
      arrayAdapter.add(getString(R.string.tab_lib_reference_statistics))
      actionMap[arrayAdapter.count - 1] = {
        val refName = item.rule?.libName ?: componentName
        val libType = if (item.rule?.libType == ACTION_IN_RULES) ACTION else type
        activity?.launchLibReferencePage(refName, item.rule?.label, libType, null)
      }
    }

    if (!viewModel.isApk && !viewModel.isApkPreview) {
      // Blocker
      if (this is ComponentsAnalysisFragment && BlockerManager.isSupportInteraction) {
        if (integrationBlockerList == null) {
          integrationBlockerList =
            BlockerManager().queryBlockedComponent(context, packageName)
        }
        val blockerShouldBlock =
          integrationBlockerList?.any { it.name == fullComponentName } == false
        val blockStr = if (blockerShouldBlock) {
          R.string.integration_blocker_menu_block
        } else {
          R.string.integration_blocker_menu_unblock
        }
        arrayAdapter.add(getString(blockStr))
        actionMap[arrayAdapter.count - 1] = {
          BlockerManager().apply {
            addBlockedComponent(
              context,
              packageName,
              componentName,
              type,
              blockerShouldBlock
            )
            integrationBlockerList =
              queryBlockedComponent(context, packageName)
            val shouldTurnToDisable =
              integrationBlockerList?.any { it.name == fullComponentName } == true && blockerShouldBlock
            animateTvTitle(position, shouldTurnToDisable)
          }
        }
      }

      // MonkeyKing Purify
      if (this is ComponentsAnalysisFragment && MonkeyKingManager.isSupportInteraction) {
        if (integrationMonkeyKingBlockList == null) {
          integrationMonkeyKingBlockList =
            MonkeyKingManager().queryBlockedComponent(context, packageName)
        }
        val monkeyKingShouldBlock =
          integrationMonkeyKingBlockList?.any { it.name == componentName } == false
        if (monkeyKingShouldBlock) {
          arrayAdapter.add(getString(R.string.integration_monkey_king_menu_block))
        } else {
          arrayAdapter.add(getString(R.string.integration_monkey_king_menu_unblock))
        }
        actionMap[arrayAdapter.count - 1] = {
          MonkeyKingManager().apply {
            addBlockedComponent(
              context,
              packageName,
              componentName,
              type,
              monkeyKingShouldBlock
            )
            integrationMonkeyKingBlockList =
              queryBlockedComponent(context, packageName)
            val shouldTurnToDisable =
              integrationMonkeyKingBlockList?.any { it.name == fullComponentName } == true && monkeyKingShouldBlock
            animateTvTitle(position, shouldTurnToDisable)
          }
        }
      }

      // Anywhere-
      if (type == ACTIVITY && AnywhereManager.isSupportInteraction) {
        arrayAdapter.add(getString(R.string.integration_anywhere_menu_editor))
        actionMap[arrayAdapter.count - 1] = {
          AnywhereManager().launchActivityEditor(
            context,
            packageName,
            componentName
          )
        }
      }
    }

    BaseAlertDialogBuilder(context)
      .setAdapter(arrayAdapter) { _, which ->
        actionMap[which]?.invoke()
      }
      .show()
  }

  private fun animateTvTitle(position: Int, shouldTurnToDisable: Boolean) {
    (adapter.getViewByPosition(position, android.R.id.title) as? TextView)?.run {
      if (shouldTurnToDisable) startStrikeThroughAnimation() else reverseStrikeThroughAnimation()
    }
  }

  private suspend fun extractElf(item: LibStringItem): Result<Unit> = withContext(Dispatchers.IO) {
    Timber.d("Extract ELF: $item")

    runCatching {
      if (viewModel.isApkPreview) {
        error("not available in apk preview mode")
      }
      val elfSourcePath = item.source ?: error("elf source is null")

      val apkSourcePath = viewModel.packageInfo.applicationInfo?.sourceDir?.split(File.separator)?.toMutableList()?.apply {
        removeLast()
        addLast(item.process.toString())
      }?.joinToString(File.separator) ?: error("apk source file is invalid")

      val sourceFile = File(apkSourcePath)
      if (!sourceFile.exists() || !sourceFile.canRead()) {
        error("apk source file is invalid")
      }

      val zipFile = ZipFileCompat(sourceFile)
      try {
        val entry = zipFile.getEntry(elfSourcePath) ?: error("Failed to find elf entry")
        val sourceElfInputStream = zipFile.getInputStream(entry)

        val packageName = viewModel.packageInfo.packageName
        val dir = BuildConfig.APPLICATION_ID +
          File.separator + packageName +
          File.separator + elfSourcePath.take(elfSourcePath.lastIndexOf(File.separator))
        Timber.d("dir = $dir")

        if (OsUtils.atLeastQ()) {
          val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, item.name)
            put(MediaStore.MediaColumns.MIME_TYPE, "application/octet-stream")
            put(
              MediaStore.MediaColumns.RELATIVE_PATH,
              Environment.DIRECTORY_DOWNLOADS + File.separator + dir
            )
          }

          val context = context ?: error("Context is null")
          val uri = context.contentResolver.insert(
            MediaStore.Downloads.EXTERNAL_CONTENT_URI,
            contentValues
          ) ?: error("Failed to create MediaStore entry")

          context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            sourceElfInputStream.use { inputStream ->
              inputStream.copyTo(outputStream)
            }
          } ?: error("Failed to open output stream")
        } else {
          val downloadsDir =
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
          val parentDir = downloadsDir.resolve(dir)
          if (!parentDir.exists()) {
            parentDir.mkdirs()
          }

          val targetFile = parentDir.resolve(item.name)
          targetFile.createNewFile()

          sourceElfInputStream.use { inputStream ->
            targetFile.outputStream().use { outputStream ->
              inputStream.copyTo(outputStream)
            }
          }
        }
      } finally {
        zipFile.close()
      }
      Unit
    }
  }
}
